﻿using System;
using System.Threading;
using System.Collections.Generic;
using Studio.Network.Kaixin001.Entities;

namespace Studio.Network.Kaixin001
{
    
    public class Utilities
    {

        private static Random _randomGenerator = new Random(DateTime.Now.Millisecond);

        public static Random RandomGenerator
        {
            get { return Utilities._randomGenerator; }
        }

        private static long[] ServerSpeed()
        {
            return ServerSpeed(KXInterface.KAIXIN001_SERVERS);
        }

        public static long[] ServerSpeed(string[] servers)
        {

            int ServerCount = servers.Length;

            int[] timeConsumed = new int[ServerCount];
            Thread[] threads = new Thread[ServerCount];
            ResponseThread[] threadObjects = new ResponseThread[ServerCount];

            long MaxResponseTime = 1;
            int MaxBrick = 50;
            long[] result = new long[ServerCount];


            for (int i = 0; i < ServerCount; i++)
            {
                threadObjects[i] = new ResponseThread(i, 10000, servers[i]);
                threads[i] = new Thread(new ThreadStart(threadObjects[i].Seek));
                threads[i].Start();
            }
            
            for (int i = 0; i < ServerCount; i++)
            {
                // Wait thread finish
                threads[i].Join();
                // Poll max response time thread
                long time = threadObjects[i].ResponseTime;
                if (time > MaxResponseTime && ResponseThread.RESPONSE_FAILED != time) MaxResponseTime = time;
                // Save to result
                result[i] = threadObjects[i].ResponseTime;
            }

            for (int i = 0; i < ServerCount; i++)
            {
                long time = threadObjects[i].ResponseTime;
                long BrickCount = MaxBrick * time / MaxResponseTime;
                string Bricks = string.Empty;
                string Brick = "I";
                if (ResponseThread.RESPONSE_FAILED != time)
                    for (int x = 0; x < BrickCount; x++) Bricks += Brick;
                else
                    Bricks = "X";
                Console.WriteLine("{0:D2}.[{1}][{2:D5}]{3}", i, servers[i], time / 10000, Bricks);
            }

            return result;
        }

        public static string[] OptimizeServer(string[] initialServers)
        {
            long[] speeds = Utilities.ServerSpeed(initialServers);
            IList<ISortable> serverList = new List<ISortable>();

            for (int i = 0; i < initialServers.Length; i++)
                if(ResponseThread.RESPONSE_FAILED != speeds[i])
                    serverList.Add(new SortableServer(initialServers[i], speeds[i]));

            string[] selectedServers = null;

            // Copy to default servers
            if (serverList.Count > 0)
            {
                ISortable[] servers = new ISortable[serverList.Count];
                serverList.CopyTo(servers, 0);
                selectedServers = new string[serverList.Count];
                Utilities.QuickSort(servers, 0, servers.Length - 1);
                for (int i = 0; i < serverList.Count; i++) selectedServers[i] = servers[i].Data.ToString();
            }

            Console.WriteLine("Sorted SERVERS:");
            for (int i = 0; i < selectedServers.Length; i++) Console.WriteLine("{0:D2}.[{1}]", i, selectedServers[i]);

            return selectedServers;
        }

        public static void QuickSort(ISortable[] elements, int start, int end)
        {
            if (start >= end) return;
            int pivot = Partition(elements, start, end);
            QuickSort(elements, start, pivot);
            QuickSort(elements, pivot + 1, end);
        }

        static int Partition(ISortable[] elements, int start, int end)
        {
            // Select Pivot
            int pivot = RandomGenerator.Next(start, end);
            while (start < end)
            {
                while (start < pivot && 0 >= elements[start].CompareTo(elements[pivot])) start++;
                if (start < pivot)
                {
                    elements[start].SwapWith(elements[pivot]);
                    pivot = start++;
                }
                while (pivot < end && 0 <= elements[end].CompareTo(elements[pivot])) end--;
                if (pivot < end) 
                {
                    elements[end].SwapWith(elements[pivot]);
                    pivot = end--;
                }
            }
            return pivot;
        }


        public static int C(int n, int m)
        {
            int ret, i;

            ret = 1;
            for (i = n - m + 1; i <= n; i++)
                ret *= i;
            for (i = 2; i <= m; i++)
                ret /= i;

            return ret;
        }

        /// <summary>
        /// C(n, m)
        /// </summary>
        /// <param name="n"></param>
        /// <param name="m"></param>
        /// <param name="o">index of C(n, m)</param>
        /// <param name="b">sizeof(b) == C(n, m)</param>
        /// <param name="p">pointer of b</param>
        public static void Combination(int n, int m, int o, byte[] b, int p)
        {
            int t = C(n - 1, m - 1);

            if (n == 0)
                return;

            if (m != 0 && t > o)
            {
                b[p] = 1;
                Combination(n - 1, m - 1, o, b, p + 1);
            }
            else
            {
                b[p] = 0;
                Combination(n - 1, m, o - t, b, p + 1);
            }
        }

        /// <summary>
        /// Calculate cars combinations with N cars satisfy  N1 <= SIGMA(price) <= N2
        /// </summary>
        public static IList<IList<Car>> CombinationCars(IList<Car> cars, int m, int N1, int N2)
        {
            IList<IList<Car>> carsList = new List<IList<Car>>();
            // Assert
            int n = cars.Count;
            if (n < m) m = n;
            if (N1 > N2) return carsList;
            // 
            int possible = Utilities.C(n, m);
            byte[] b = new byte[n];
            // for each possibility
            for (int i = 0; i < possible; i++)
            { 
                // Calculate Combination
                Utilities.Combination(n, m, i, b, 0);
                IList<Car> selected = new List<Car>();
                int N = 0;
                for (int x = 0; x < n; x++)
                {
                    if (1 == b[x])
                    {
                        Car selectedCar = cars[x];
                        N += selectedCar.price;
                        selected.Add(selectedCar.Clone() as Car);
                    }
                }
                if (N1 <= N && N <= N2) carsList.Add(selected);
            }
            return carsList;
        }

    }

    public interface ISortable
    {
        long CompareTo(ISortable element);
        void SwapWith(ISortable element);
        object Data { get; set; }
    }

    public class SortableServer : ISortable
    {
        long responseTime;
        string address;

        public SortableServer(string address, long responseTime)
        {
            this.address = address;
            this.responseTime = responseTime;
        }

        public long CompareTo(ISortable element)
        {
            if (element is SortableServer)
                return this.responseTime - (element as SortableServer).responseTime;
            else
                return 0;
        }

        public void SwapWith(ISortable element)
        {
            // Swap response time
            if (element is SortableServer)
            {
                SortableServer server = element as SortableServer;
                this.responseTime += server.responseTime;
                server.responseTime = this.responseTime - server.responseTime;
                this.responseTime -= server.responseTime;
            }
            // Swap address
            object data = this.Data;
            this.Data = element.Data;
            element.Data = data;
        }

        public override string ToString()
        {
            return string.Format("SERVER:{0}\tRT:{1}", this.address, this.responseTime);
        }

        public object Data
        {
            get { return this.address; }
            set { this.address = value.ToString(); }
        }

    }
}
